// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.

// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// OpenEthereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with OpenEthereum.  If not, see <http://www.gnu.org/licenses/>.

//! Types used in Confirmations queue (Trusted Signer)

use ansi_term::Colour;
use bytes::ToPretty;
use serde::{Serialize, Serializer};
use std::fmt;

use ethereum_types::{H160, H256, H520, U256};
use ethkey::Password;
use v1::{
    helpers,
    types::{Bytes, Origin, RichRawTransaction, TransactionCondition, TransactionRequest},
};

/// Confirmation waiting in a queue
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfirmationRequest {
    /// Id of this confirmation
    pub id: U256,
    /// Payload
    pub payload: ConfirmationPayload,
    /// Request origin
    pub origin: Origin,
}

impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
    fn from(c: helpers::ConfirmationRequest) -> Self {
        ConfirmationRequest {
            id: c.id,
            payload: c.payload.into(),
            origin: c.origin,
        }
    }
}

impl fmt::Display for ConfirmationRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "#{}: {} coming from {}",
            self.id, self.payload, self.origin
        )
    }
}

impl fmt::Display for ConfirmationPayload {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction),
            ConfirmationPayload::SignTransaction(ref transaction) => {
                write!(f, "(Sign only) {}", transaction)
            }
            ConfirmationPayload::EthSignMessage(ref sign) => write!(f, "{}", sign),
            ConfirmationPayload::EIP191SignMessage(ref sign) => write!(f, "{}", sign),
            ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt),
        }
    }
}

/// Ethereum-prefixed Sign request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EthSignRequest {
    /// Address
    pub address: H160,
    /// Hash to sign
    pub data: Bytes,
}

/// EIP191 Sign request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EIP191SignRequest {
    /// Address
    pub address: H160,
    /// Hash to sign
    pub data: H256,
}

impl From<(H160, H256)> for EIP191SignRequest {
    fn from(tuple: (H160, H256)) -> Self {
        EIP191SignRequest {
            address: tuple.0,
            data: tuple.1,
        }
    }
}

impl fmt::Display for EIP191SignRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "sign 0x{} with {}",
            self.data.0.pretty(),
            Colour::White.bold().paint(format!("0x{:?}", self.address)),
        )
    }
}

impl From<(H160, Bytes)> for EthSignRequest {
    fn from(tuple: (H160, Bytes)) -> Self {
        EthSignRequest {
            address: tuple.0,
            data: tuple.1,
        }
    }
}

impl fmt::Display for EthSignRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "sign 0x{} with {}",
            self.data.0.pretty(),
            Colour::White.bold().paint(format!("0x{:?}", self.address)),
        )
    }
}

/// Decrypt request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DecryptRequest {
    /// Address
    pub address: H160,
    /// Message to decrypt
    pub msg: Bytes,
}

impl From<(H160, Bytes)> for DecryptRequest {
    fn from(tuple: (H160, Bytes)) -> Self {
        DecryptRequest {
            address: tuple.0,
            msg: tuple.1,
        }
    }
}

impl fmt::Display for DecryptRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "decrypt data with {}",
            Colour::White.bold().paint(format!("0x{:?}", self.address)),
        )
    }
}

/// Confirmation response for particular payload
#[derive(Debug, Clone, PartialEq)]
pub enum ConfirmationResponse {
    /// Transaction Hash
    SendTransaction(H256),
    /// Transaction RLP
    SignTransaction(RichRawTransaction),
    /// Signature (encoded as VRS)
    Signature(H520),
    /// Decrypted data
    Decrypt(Bytes),
}

impl Serialize for ConfirmationResponse {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            ConfirmationResponse::SendTransaction(ref hash) => hash.serialize(serializer),
            ConfirmationResponse::SignTransaction(ref rlp) => rlp.serialize(serializer),
            ConfirmationResponse::Signature(ref signature) => signature.serialize(serializer),
            ConfirmationResponse::Decrypt(ref data) => data.serialize(serializer),
        }
    }
}

/// Confirmation response with additional token for further requests
#[derive(Clone, PartialEq, Serialize)]
pub struct ConfirmationResponseWithToken {
    /// Actual response
    pub result: ConfirmationResponse,
    /// New token
    pub token: Password,
}

/// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub enum ConfirmationPayload {
    /// Send Transaction
    SendTransaction(TransactionRequest),
    /// Sign Transaction
    SignTransaction(TransactionRequest),
    /// Signature
    #[serde(rename = "sign")]
    EthSignMessage(EthSignRequest),
    /// signature without prefix
    EIP191SignMessage(EIP191SignRequest),
    /// Decryption
    Decrypt(DecryptRequest),
}

impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
    fn from(c: helpers::ConfirmationPayload) -> Self {
        match c {
            helpers::ConfirmationPayload::SendTransaction(t) => {
                ConfirmationPayload::SendTransaction(t.into())
            }
            helpers::ConfirmationPayload::SignTransaction(t) => {
                ConfirmationPayload::SignTransaction(t.into())
            }
            helpers::ConfirmationPayload::EthSignMessage(address, data) => {
                ConfirmationPayload::EthSignMessage(EthSignRequest {
                    address,
                    data: data.into(),
                })
            }
            helpers::ConfirmationPayload::SignMessage(address, data) => {
                ConfirmationPayload::EIP191SignMessage(EIP191SignRequest { address, data })
            }
            helpers::ConfirmationPayload::Decrypt(address, msg) => {
                ConfirmationPayload::Decrypt(DecryptRequest {
                    address,
                    msg: msg.into(),
                })
            }
        }
    }
}

/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct TransactionModification {
    /// Modified transaction sender
    pub sender: Option<H160>,
    /// Modified gas price
    pub gas_price: Option<U256>,
    /// Modified gas
    pub gas: Option<U256>,
    /// Modified transaction condition.
    pub condition: Option<Option<TransactionCondition>>,
}

/// Represents two possible return values.
#[derive(Debug, Clone)]
pub enum Either<A, B>
where
    A: fmt::Debug + Clone,
    B: fmt::Debug + Clone,
{
    /// Primary value
    Either(A),
    /// Secondary value
    Or(B),
}

impl<A, B> From<A> for Either<A, B>
where
    A: fmt::Debug + Clone,
    B: fmt::Debug + Clone,
{
    fn from(a: A) -> Self {
        Either::Either(a)
    }
}

impl<A, B> Serialize for Either<A, B>
where
    A: Serialize + fmt::Debug + Clone,
    B: Serialize + fmt::Debug + Clone,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            Either::Either(ref a) => a.serialize(serializer),
            Either::Or(ref b) => b.serialize(serializer),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ethereum_types::{Address, H256, U256};
    use serde_json;
    use std::str::FromStr;
    use v1::{helpers, types::TransactionCondition};

    #[test]
    fn should_serialize_sign_confirmation() {
        // given
        let request = helpers::ConfirmationRequest {
            id: 15.into(),
            payload: helpers::ConfirmationPayload::EthSignMessage(
                Address::from_low_u64_be(1),
                vec![5].into(),
            ),
            origin: Origin::Rpc("test service".into()),
        };

        // when
        let res = serde_json::to_string(&ConfirmationRequest::from(request));
        let expected = r#"{"id":"0xf","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}},"origin":{"rpc":"test service"}}"#;

        // then
        assert_eq!(res.unwrap(), expected.to_owned());
    }

    #[test]
    fn should_serialize_transaction_confirmation() {
        // given
        let request = helpers::ConfirmationRequest {
            id: 15.into(),
            payload: helpers::ConfirmationPayload::SendTransaction(
                helpers::FilledTransactionRequest {
                    transaction_type: Default::default(),
                    from: Address::from_low_u64_be(0),
                    used_default_from: false,
                    to: None,
                    gas: 15_000.into(),
                    gas_price: Some(10_000.into()),
                    max_fee_per_gas: None,
                    value: 100_000.into(),
                    data: vec![1, 2, 3],
                    nonce: Some(1.into()),
                    condition: None,
                    access_list: None,
                    max_priority_fee_per_gas: None,
                },
            ),
            origin: Origin::Signer {
                session: H256::from_low_u64_be(5),
            },
        };

        // when
        let res = serde_json::to_string(&ConfirmationRequest::from(request));
        let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":{"signer":{"session":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#;

        // then
        assert_eq!(res.unwrap(), expected.to_owned());
    }

    #[test]
    fn should_serialize_sign_transaction_confirmation() {
        // given
        let request = helpers::ConfirmationRequest {
            id: 15.into(),
            payload: helpers::ConfirmationPayload::SignTransaction(
                helpers::FilledTransactionRequest {
                    transaction_type: Default::default(),
                    from: Address::from_low_u64_be(0),
                    used_default_from: false,
                    to: None,
                    gas: 15_000.into(),
                    gas_price: Some(10_000.into()),
                    max_fee_per_gas: None,
                    value: 100_000.into(),
                    data: vec![1, 2, 3],
                    nonce: Some(1.into()),
                    condition: None,
                    access_list: None,
                    max_priority_fee_per_gas: None,
                },
            ),
            origin: Origin::Unknown,
        };

        // when
        let res = serde_json::to_string(&ConfirmationRequest::from(request));
        let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}},"origin":"unknown"}"#;

        // then
        assert_eq!(res.unwrap(), expected.to_owned());
    }

    #[test]
    fn should_serialize_decrypt_confirmation() {
        // given
        let request = helpers::ConfirmationRequest {
            id: 15.into(),
            payload: helpers::ConfirmationPayload::Decrypt(
                Address::from_low_u64_be(10),
                vec![1, 2, 3].into(),
            ),
            origin: Default::default(),
        };

        // when
        let res = serde_json::to_string(&ConfirmationRequest::from(request));
        let expected = r#"{"id":"0xf","payload":{"decrypt":{"address":"0x000000000000000000000000000000000000000a","msg":"0x010203"}},"origin":"unknown"}"#;

        // then
        assert_eq!(res.unwrap(), expected.to_owned());
    }

    #[test]
    fn should_deserialize_modification() {
        // given
        let s1 = r#"{
			"sender": "0x000000000000000000000000000000000000000a",
			"gasPrice":"0xba43b7400",
			"condition": { "block": 66 }
		}"#;
        let s2 = r#"{"gas": "0x1233"}"#;
        let s3 = r#"{}"#;

        // when
        let res1: TransactionModification = serde_json::from_str(s1).unwrap();
        let res2: TransactionModification = serde_json::from_str(s2).unwrap();
        let res3: TransactionModification = serde_json::from_str(s3).unwrap();

        // then
        assert_eq!(
            res1,
            TransactionModification {
                sender: Some(Address::from_low_u64_be(10)),
                gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
                gas: None,
                condition: Some(Some(TransactionCondition::Number(0x42))),
            }
        );
        assert_eq!(
            res2,
            TransactionModification {
                sender: None,
                gas_price: None,
                gas: Some(U256::from_str("1233").unwrap()),
                condition: None,
            }
        );
        assert_eq!(
            res3,
            TransactionModification {
                sender: None,
                gas_price: None,
                gas: None,
                condition: None,
            }
        );
    }

    #[test]
    fn should_serialize_confirmation_response_with_token() {
        // given
        let response = ConfirmationResponseWithToken {
            result: ConfirmationResponse::SendTransaction(H256::default()),
            token: "test-token".into(),
        };

        // when
        let res = serde_json::to_string(&response);
        let expected = r#"{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","token":"test-token"}"#;

        // then
        assert_eq!(res.unwrap(), expected.to_owned());
    }
}
